#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of Beremiz, a Integrated Development Environment for
# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
#
# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
#
# See COPYING file for copyrights details.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
import csv
import re

import wx
import wx.grid
import wx.lib.buttons

from resources import GetBitmap
from .CustomGrid import CustomGrid
from .CustomTable import CustomTable
from .LocationCellEditor import LocationCellEditor
from .SelectionCellEditor import SelectionCellEditor
from ..PLCControler import VARIABLE_NAME_SUFFIX_MODEL
from ..check import check_var_name
from ..dialogs.ArrayTypeDialog import ArrayTypeDialog
from ..graphics.GraphicCommons import ERROR_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD
from ..plcopen.VariableInfoCollector import _VariableInfos
from ..plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS, DefaultType
from ..util.TranslationCatalogs import NoTranslate

# -------------------------------------------------------------------------------
#                                 Helpers
# -------------------------------------------------------------------------------
_ = wx.GetTranslation

[
    TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE,
    POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
] = range(10)


def GetVariableTableColnames(location):
    _ = NoTranslate
    cols = ["#",
            _("Name"),
            _("Class"),
            _("Type"),
            _("Location"),
            _("Initial Value"),
            _("Option"),
            _("Documentation")]
    if not location:
        del cols[4]  # remove 'Location' column
    return cols


def GetOptions(constant=True, retain=True, non_retain=True):
    _ = NoTranslate
    options = [""]
    if constant:
        options.append(_("Constant"))
    if retain:
        options.append(_("Retain"))
    if non_retain:
        options.append(_("Non-Retain"))
    return options


def GetFilterChoiceTransfer():
    _ = NoTranslate
    return {_("All"): _("All"), _("Interface"): _("Interface"),
            _("   Input"): _("Input"), _("   Output"): _("Output"), _("   InOut"): _("InOut"),
            _("   External"): _("External"), _("Variables"): _("Variables"), _("   Local"): _("Local"),
            _("   Temp"): _("Temp"), _("Global"): _("Global")}  # , _("Access") : _("Access")}


CheckOptionForClass = {
    "Local": lambda x: x,
    "Temp": lambda x: "",
    "Input": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""),
    "InOut": lambda x: "",
    "Output": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""),
    "Global": lambda x: {"Constant": "Constant", "Retain": "Retain"}.get(x, ""),
    "External": lambda x: {"Constant": "Constant"}.get(x, "")
}

LOCATION_MODEL = re.compile(r"((?:%[IQM](?:\*|(?:[XBWLD]?[0-9]+(?:\.[0-9]+)*)))?)$")
LOCATION_MODEL_SET = re.compile(r"((?:%[IQM](?:[XBWLD]?[0-9]+(?:\.[0-9]+)*))?)$")


# -------------------------------------------------------------------------------
#                            Variables Panel Table
# -------------------------------------------------------------------------------


class VariableTable(CustomTable):
    """
    A custom wx.grid.Grid Table using user supplied data
    """

    def __init__(self, parent, data, colnames):
        # The base class must be initialized *first*
        CustomTable.__init__(self, parent, data, colnames)
        self.old_value = None
        self.OPTIONS_DICT = dict([(_(option), option)
                                  for option in GetOptions()])
        self.VARIABLE_CLASSES_DICT = dict([(_(_class), _class)
                                           for _class in list(GetFilterChoiceTransfer().values())])
        self.search = None

    def GetValueByName(self, row, colname):
        if row < self.GetNumberRows():
            if colname == "Initial Value":
                colname = "InitialValue"
            return getattr(self.data[row], colname)

    def SetValueByName(self, row, colname, value):
        if row < self.GetNumberRows():
            setattr(self.data[row], colname, value)

    def GetValue(self, row, col):
        if row < self.GetNumberRows():
            if col == 0:
                return self.data[row].Number
            colname = self.GetColLabelValue(col, False)
            if colname == "Initial Value":
                colname = "InitialValue"
            value = getattr(self.data[row], colname, "")
            if colname == "Type" and isinstance(value, tuple) and value[0] == "array":
                return "ARRAY [%s] OF %s" % (",".join(map("..".join, value[2])), value[1])
            if not isinstance(value, str):
                value = str(value)
            if colname in ["Class", "Option"]:
                return _(value)
            return value

    def SetValue(self, row, col, value):
        if col < len(self.colnames):
            colname = self.GetColLabelValue(col, False)
            if colname == "Name":
                self.old_value = getattr(self.data[row], colname)
            elif colname == "Class":
                value = self.VARIABLE_CLASSES_DICT[value]
                self.SetValueByName(row, "Option", CheckOptionForClass[value](self.GetValueByName(row, "Option")))
                if value == "External":
                    self.SetValueByName(row, "InitialValue", "")
            elif colname == "Option":
                value = self.OPTIONS_DICT[value]
            elif colname == "Initial Value":
                colname = "InitialValue"
            setattr(self.data[row], colname, value)

    def GetOldValue(self):
        return self.old_value

    def _GetRowEdit(self, row):
        row_edit = self.GetValueByName(row, "Edit")
        var_type = self.Parent.GetTagName()
        bodytype = self.Parent.Controler.GetEditedElementBodyType(var_type)
        if bodytype in ["ST", "IL"]:
            row_edit = True
        return row_edit

    def _updateColAttrs(self, grid, rows=None, destory=False):
        """
        wx.grid.Grid -> update the column attributes to add the
        appropriate renderer given the column name.

        Otherwise default to the default renderer.
        """
        for row in range(self.GetNumberRows()):
            var_class = self.GetValueByName(row, "Class")
            var_type = self.GetValueByName(row, "Type")
            row_highlights = self.Highlights.get(row, {})
            for col in range(self.GetNumberCols()):
                editor = None
                renderer = None
                colname = self.GetColLabelValue(col, False)
                if self.Parent.Debug:
                    grid.SetReadOnly(row, col, True)
                else:
                    if colname == "Option":
                        options = GetOptions(constant=var_class in ["Local", "External", "Global"],
                                             retain=self.Parent.ElementType != "function" and var_class in ["Local",
                                                                                                            "Input",
                                                                                                            "Output",
                                                                                                            "Global"],
                                             non_retain=self.Parent.ElementType != "function" and var_class in ["Local",
                                                                                                                "Input",
                                                                                                                "Output"])
                        if len(options) > 1:
                            editor = SelectionCellEditor(self, row, colname, list(map(_, options)))
                            # editor.SetParameters(",".join(map(_, options)))
                        else:
                            grid.SetReadOnly(row, col, True)
                    elif col != 0 and self._GetRowEdit(row):
                        grid.SetReadOnly(row, col, False)
                        if colname == "Name":
                            # editor = CellTextEditor()
                            renderer = wx.grid.GridCellStringRenderer()
                        elif colname == "Initial Value":
                            if var_class not in ["External", "InOut"]:
                                if self.Parent.Controler.IsEnumeratedType(var_type):
                                    editor = SelectionCellEditor(self, row, colname, list(
                                        [""] + self.Parent.Controler.GetEnumeratedDataValues(var_type)))
                                    # editor.SetParameters(
                                    #     ",".join([""] + self.Parent.Controler.GetEnumeratedDataValues(var_type)))
                                # else:
                                #     editor = CellTextEditor()
                                renderer = wx.grid.GridCellStringRenderer()
                            else:
                                grid.SetReadOnly(row, col, True)
                        elif colname == "Location":
                            if var_class in ["Local", "Global"] and self.Parent.Controler.IsLocatableType(var_type):
                                editor = LocationCellEditor(self, self.Parent.Controler)
                                renderer = wx.grid.GridCellStringRenderer()
                            else:
                                grid.SetReadOnly(row, col, True)
                        elif colname == "Class":
                            if len(self.Parent.ClassList) == 1:
                                grid.SetReadOnly(row, col, True)
                            else:
                                excluded = []
                                if self.Parent.IsFunctionBlockType(var_type):
                                    excluded.extend(["Local", "Temp"])
                                editor = SelectionCellEditor(self, row, colname, list(
                                    [_(choice) for choice in self.Parent.ClassList if choice not in excluded]))
                                # editor.SetParameters(
                                #     ",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded]))
                    elif colname != "Documentation":
                        grid.SetReadOnly(row, col, True)

                if editor:
                    editor.IncRef()
                    grid.SetCellEditor(row, col, editor)
                grid.SetCellRenderer(row, col, renderer)

                if colname == "Location" and LOCATION_MODEL.match(self.GetValueByName(row, colname)) is None:
                    highlight_colours = ERROR_HIGHLIGHT
                else:
                    highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1]
                grid.SetCellBackgroundColour(row, col, highlight_colours[0])
                grid.SetCellTextColour(row, col, highlight_colours[1])
            self.ResizeRow(grid, row)


# -------------------------------------------------------------------------------
#                         Variable Panel Drop Target
# -------------------------------------------------------------------------------


class VariableDropTarget(wx.TextDropTarget):
    '''
    This allows dragging a variable location from somewhere to the Location
    column of a variable row.

    The drag source should be a TextDataObject containing a Python tuple like:
        ('%ID0.0.0', 'location', 'REAL')

    c_ext/CFileEditor.py has an example of this (you can drag a C extension
    variable to the Location column of the variable panel).
    '''

    def __init__(self, parent):
        wx.TextDropTarget.__init__(self)
        self.ParentWindow = parent

    def OnDropText(self, x, y, data):
        self.ParentWindow.ParentWindow.Select()
        x, y = self.ParentWindow.VariablesGrid.CalcUnscrolledPosition(x, y)
        col = self.ParentWindow.VariablesGrid.XToCol(x)
        row = self.ParentWindow.VariablesGrid.YToRow(y)
        message = None
        element_type = self.ParentWindow.ElementType
        try:
            values = eval(data)
        except Exception:
            message = _("Invalid value \"%s\" for variable grid element") % data
            values = None
        if not isinstance(values, tuple):
            message = _("Invalid value \"%s\" for variable grid element") % data
            values = None
        if values is not None:
            if col != wx.NOT_FOUND and row != wx.NOT_FOUND:
                colname = self.ParentWindow.Table.GetColLabelValue(col, False)
                if colname == "Location" and values[1] == "location":
                    if not self.ParentWindow.Table.GetValueByName(row, "Edit"):
                        message = _("Can't give a location to a function block instance")
                    elif self.ParentWindow.Table.GetValueByName(row, "Class") not in ["Local", "Global"]:
                        message = _("Can only give a location to local or global variables")
                    else:
                        location = values[0]
                        variable_type = self.ParentWindow.Table.GetValueByName(row, "Type")
                        base_type = self.ParentWindow.Controler.GetBaseType(variable_type)

                        if values[2] is not None:
                            base_location_type = self.ParentWindow.Controler.GetBaseType(values[2])
                            if values[2] != variable_type and base_type != base_location_type:
                                message = _("Incompatible data types between \"{a1}\" and \"{a2}\""). \
                                    format(a1=values[2], a2=variable_type)

                        if message is None:
                            if not location.startswith("%"):
                                if location[0].isdigit() and base_type != "BOOL":
                                    message = _("Incompatible size of data between \"%s\" and \"BOOL\"") % location
                                elif location[0] not in LOCATIONDATATYPES:
                                    message = _("Unrecognized data size \"%s\"") % location[0]
                                elif base_type not in LOCATIONDATATYPES[location[0]]:
                                    message = _("Incompatible size of data between \"{a1}\" and \"{a2}\""). \
                                        format(a1=location, a2=variable_type)
                                else:
                                    dialog = wx.SingleChoiceDialog(
                                        self.ParentWindow.ParentWindow.ParentWindow,
                                        _("Select a variable class:"),
                                        _("Variable class"),
                                        [_("Input"), _("Output"), _("Memory")],
                                        wx.DEFAULT_DIALOG_STYLE | wx.OK | wx.CANCEL)
                                    if dialog.ShowModal() == wx.ID_OK:
                                        selected = dialog.GetSelection()
                                    else:
                                        selected = None
                                    dialog.Destroy()
                                    if selected is None:
                                        return False
                                    if selected == 0:
                                        location = "%I" + location
                                    elif selected == 1:
                                        location = "%Q" + location
                                    else:
                                        location = "%M" + location

                            if message is None:
                                self.ParentWindow.Table.SetValue(row, col, location)
                                self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
                                self.ParentWindow.SaveValues()
                elif colname == "Initial Value" and values[1] == "Constant":
                    if not self.ParentWindow.Table.GetValueByName(row, "Edit"):
                        message = _("Can't set an initial value to a function block instance")
                    else:
                        self.ParentWindow.Table.SetValue(row, col, values[0])
                        self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
                        self.ParentWindow.SaveValues()
            elif (element_type not in ["config", "resource", "function"] and values[1] == "Global" and
                  self.ParentWindow.Filter in ["All", "Interface", "External"] or
                  element_type != "function" and values[1] in ["location", "NamedConstant"]):
                if values[1] in ["location", "NamedConstant"]:
                    var_name = values[3]
                else:
                    var_name = values[0]
                tagname = self.ParentWindow.GetTagName()
                dlg = wx.TextEntryDialog(
                    self.ParentWindow.ParentWindow.ParentWindow,
                    _("Confirm or change variable name"),
                    _('Variable Drop'), var_name)
                dlg.SetValue(var_name)
                var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None
                dlg.Destroy()
                if var_name is None:
                    return False
                elif var_name.upper() in [
                    name.upper() for name in
                    self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
                    message = _("\"%s\" pou already exists!") % var_name
                elif not var_name.upper() in [
                    name.upper()
                    for name in self.ParentWindow.Controler.
                            GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
                    var_infos = self.ParentWindow.DefaultValue.copy()
                    var_infos.Name = var_name
                    var_infos.Type = values[2]
                    var_infos.Documentation = values[4]
                    if values[1] == "location":
                        location = values[0]
                        if not location.startswith("%"):
                            dialog = wx.SingleChoiceDialog(
                                self.ParentWindow.ParentWindow.ParentWindow,
                                _("Select a variable class:"),
                                _("Variable class"),
                                [_("Input"), _("Output"), _("Memory")],
                                wx.DEFAULT_DIALOG_STYLE | wx.OK | wx.CANCEL)
                            if dialog.ShowModal() == wx.ID_OK:
                                selected = dialog.GetSelection()
                            else:
                                selected = None
                            dialog.Destroy()
                            if selected is None:
                                return False
                            if selected == 0:
                                location = "%I" + location
                            elif selected == 1:
                                location = "%Q" + location
                            else:
                                location = "%M" + location
                        self.ParentWindow.Controler.check_location(location)
                        if element_type == "functionBlock":
                            configs = self.ParentWindow.Controler.GetProjectConfigNames(
                                self.ParentWindow.Debug)
                            if len(configs) == 0:
                                return False
                            if not var_name.upper() in [name.upper() for name in
                                                        self.ParentWindow.Controler.GetConfigurationVariableNames(
                                                            configs[0])]:
                                self.ParentWindow.Controler.AddConfigurationGlobalVar(
                                    configs[0], values[2], var_name, location, "")
                            var_infos.Class = "External"
                        else:
                            if element_type == "program":
                                var_infos.Class = "Local"
                            else:
                                var_infos.Class = "Global"
                            var_infos.Location = location
                    elif values[1] == "NamedConstant":
                        if element_type in ["functionBlock", "program"]:
                            var_infos.Class = "Local"
                            var_infos.InitialValue = values[0]
                        else:
                            return False
                    else:
                        var_infos.Class = "External"
                    var_infos.Number = len(self.ParentWindow.Values)
                    self.ParentWindow.Values.append(var_infos)
                    self.ParentWindow.SaveValues()
                    self.ParentWindow.RefreshValues()
                else:
                    message = _("\"%s\" element for this pou already exists!") % var_name

        if message is not None:
            wx.CallAfter(self.ShowMessage, message)
            return False
        return True

    def ShowMessage(self, message):
        message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK | wx.ICON_ERROR)
        message.ShowModal()
        message.Destroy()


# -------------------------------------------------------------------------------
#                               Variable Panel
# -------------------------------------------------------------------------------


class VariablePanel(wx.Panel):

    def __init__(self, parent, window, controler, element_type, debug=False):
        wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)

        self.DownButton = None
        self.UpButton = None
        self.DeleteButton = None
        self.AddButton = None
        self.VARIABLE_CHOICES_DICT = dict([(_(_class), _class) for
                                           _class in list(GetFilterChoiceTransfer().keys())])

        self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0)
        self.MainSizer.AddGrowableCol(0)
        self.MainSizer.AddGrowableRow(1)

        self.controls_sizer = wx.FlexGridSizer(cols=17, hgap=5, rows=1, vgap=5)
        self.controls_sizer.AddGrowableCol(7)
        self.controls_sizer.AddGrowableRow(0)
        self.MainSizer.Add(self.controls_sizer, border=5, flag=wx.GROW | wx.ALL)

        self.ReturnTypeLabel = wx.StaticText(self, label=_('Return Type:'))
        self.controls_sizer.Add(self.ReturnTypeLabel, flag=wx.ALIGN_CENTER_VERTICAL)

        self.ReturnType = wx.ComboBox(self,
                                      size=wx.Size(145, -1), style=wx.CB_READONLY)
        self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, self.ReturnType)
        self.controls_sizer.Add(self.ReturnType)

        self.DescriptionLabel = wx.StaticText(self, label=_('Description:'))
        self.controls_sizer.Add(self.DescriptionLabel, flag=wx.ALIGN_CENTER_VERTICAL)

        self.Description = wx.TextCtrl(self,
                                       size=wx.Size(250, -1), style=wx.TE_PROCESS_ENTER)
        self.Bind(wx.EVT_TEXT_ENTER, self.OnDescriptionChanged, self.Description)
        self.Description.Bind(wx.EVT_KILL_FOCUS, self.OnDescriptionChanged)
        self.controls_sizer.Add(self.Description)

        class_filter_label = wx.StaticText(self, label=_('Class Filter:'))
        self.controls_sizer.Add(class_filter_label, flag=wx.ALIGN_CENTER_VERTICAL)

        self.ClassFilter = wx.ComboBox(self,
                                       size=wx.Size(145, -1), style=wx.CB_READONLY)
        self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, self.ClassFilter)
        self.controls_sizer.Add(self.ClassFilter)

        search_label = wx.StaticText(self, label=_('Search:'))
        self.controls_sizer.Add(search_label, flag=wx.ALIGN_CENTER_VERTICAL)
        self.showVarList = True

        self.search_input = wx.TextCtrl(self, size=wx.Size(100, -1), style=wx.TE_PROCESS_ENTER)
        self.search_input.Bind(wx.EVT_TEXT, self.mSearch)
        self.controls_sizer.Add(self.search_input)
        self.loaded = False
        for name, bitmap, Help in [
            ("AddButton", "add_element", _("Add variable")),
            ("DeleteButton", "remove_element", _("Remove variable")),
            ("UpButton", "up", _("Move variable up")),
            ("DownButton", "down", _("Move variable down"))]:
            button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
                                                    size=wx.Size(28, 28), style=wx.NO_BORDER)
            button.SetToolTip(Help)
            setattr(self, name, button)
            self.controls_sizer.Add(button)
        debug_button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("debug_instance"), size=wx.Size(28, 28),
                                                      style=wx.NO_BORDER)
        self.controls_sizer.Add(debug_button)
        debug_button.Hide()
        importButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap('Import_Excel'), size=wx.Size(28, 28),
                                                      style=wx.NO_BORDER)
        self.controls_sizer.Add(importButton)
        importButton.Bind(wx.EVT_BUTTON, self.OnImport)
        importButton.SetToolTip('从csv文件导入变量')
        exportButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap('Export_Excel'), size=wx.Size(28, 28),
                                                      style=wx.NO_BORDER)
        self.controls_sizer.Add(exportButton)
        exportButton.Bind(wx.EVT_BUTTON, self.OnExport)
        exportButton.SetToolTip('导出变量到csv文件')

        CommentButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("COMMENT"), size=wx.Size(28, 28),
                                                       style=wx.NO_BORDER)
        self.controls_sizer.Add(CommentButton)
        CommentButton.Bind(wx.EVT_BUTTON, self.OnCommentButton)
        CommentButton.SetToolTip('打开/关闭注释显示')
        show_button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("fit"), size=wx.Size(28, 28),
                                                     style=wx.NO_BORDER)
        self.controls_sizer.Add(show_button)
        show_button.Bind(wx.EVT_BUTTON, self.OnButtonShow)
        show_button.SetToolTip('打开/折叠变量面板')
        self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL | wx.HSCROLL)
        self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED,
                                self.OnVariablesGridCellChange)
        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK,
                                self.OnVariablesGridCellLeftClick)
        self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,
                                self.OnVariablesGridEditorShown)
        self.MainSizer.Add(self.VariablesGrid, flag=wx.GROW)

        self.SetSizer(self.MainSizer)

        self.ParentWindow = window
        self.Controler = controler
        self.ElementType = element_type
        self.Debug = debug

        self.RefreshHighlightsTimer = wx.Timer(self, -1)
        self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer,
                  self.RefreshHighlightsTimer)

        self.Filter = "All"
        self.FilterChoices = []
        self.FilterChoiceTransfer = GetFilterChoiceTransfer()

        self.DefaultValue = _VariableInfos("LocalVar0", "", "", "", "", True, "", DefaultType, ([], []), 0)

        if element_type in ["config", "resource"]:
            self.DefaultTypes = {"All": "Global"}
        else:
            self.DefaultTypes = {"All": "Local", "Interface": "Input", "Variables": "Local"}

        if element_type in ["config", "resource"] \
                or element_type in ["program", "transition", "action"]:
            # this is an element that can have located variables
            self.Table = VariableTable(self, [], GetVariableTableColnames(True))

            if element_type in ["config", "resource"]:
                self.FilterChoices = ["All", "Global"]  # ,"Access"]
            else:
                self.FilterChoices = ["All",
                                      "Interface", "   Input", "   Output", "   InOut", "   External",
                                      "Variables", "   Local", "   Temp"]  # ,"Access"]

            # these condense the ColAlignements list
            left = wx.ALIGN_LEFT
            center = wx.ALIGN_CENTER

            #                        Num     Name    Class   Type    Loc     Init    Option   Doc
            self.ColSettings = {
                "size": [40, 80, 100, 80, 110, 120, 100, 160],
                "alignement": [center, left, left, left, left, left, left, left],
                "fixed_size": [True, False, True, False, True, True, True, False],
            }

        else:
            # this is an element that cannot have located variables
            self.Table = VariableTable(self, [], GetVariableTableColnames(False))

            if element_type == "function":
                self.FilterChoices = ["All",
                                      "Interface", "   Input", "   Output", "   InOut",
                                      "Variables", "   Local"]
            else:
                self.FilterChoices = ["All",
                                      "Interface", "   Input", "   Output", "   InOut", "   External",
                                      "Variables", "   Local", "   Temp"]

            # these condense the alignements list
            left = wx.ALIGN_LEFT
            center = wx.ALIGN_CENTER

            #                        Num     Name    Class   Type    Init    Option   Doc
            self.ColSettings = {
                "size": [40, 80, 100, 80, 120, 100, 160],
                "alignement": [center, left, left, left, left, left, left],
                "fixed_size": [True, False, True, True, True, True, False],
            }

        self.PanelWidthMin = sum(self.ColSettings["size"])

        self.ElementType = element_type
        self.BodyType = None

        for choice in self.FilterChoices:
            self.ClassFilter.Append(_(choice))

        reverse_transfer = {}
        for Filter, choice in self.FilterChoiceTransfer.items():
            reverse_transfer[choice] = Filter
        self.ClassFilter.SetStringSelection(_(reverse_transfer[self.Filter]))
        self.RefreshTypeList()

        self.VariablesGrid.SetTable(self.Table)
        self.VariablesGrid.SetButtons({"Add": self.AddButton,
                                       "Delete": self.DeleteButton,
                                       "Up": self.UpButton,
                                       "Down": self.DownButton})
        self.VariablesGrid.SetEditable(not self.Debug)

        def _AddVariable(new_row):
            # self.Save()
            row_content = self.DefaultValue.copy()
            if new_row > 0:
                # doesn't copy values of previous var if it's non-editable (like a FB)
                new_row = self.Table.GetValueByName(new_row - 1, "Number")
                if self.Values[new_row - 1].Edit:
                    row_content = self.Values[new_row - 1].copy()
                old_name = self.Values[new_row - 1].Name
                row_content.Name = self.GenerateNewName(self.TagName, old_name, old_name + '%d')
                check_var_name(row_content.Name)
                # increment location address
                if row_content.Location != "" and LOCATION_MODEL_SET.match(row_content.Location):
                    old_location = row_content.Location
                    model = re.compile(r"%[IQM][XBWLD]?(.*\.|)")
                    prefix = model.match(old_location).group(0)
                    addr = int(re.split(model, old_location)[-1]) + 1
                    row_content.Location = prefix + str(addr)

            if not row_content.Class:
                row_content.Class = self.DefaultTypes.get(self.Filter, self.Filter)

            if self.Filter == "All" and len(self.Values) > 0:
                self.Values.insert(new_row, row_content)
            else:
                self.Values.append(row_content)
                new_row = self.Table.GetNumberRows()
            self.SaveValues()
            if self.ElementType == "resource":
                self.ParentWindow.RefreshView(variablepanel=False)
            self.RefreshValues()
            return new_row

        setattr(self.VariablesGrid, "_AddRow", _AddVariable)

        def _DeleteVariable(row):
            if _GetRowEdit(row):
                self.Values.remove(self.Table.GetRow(row))
                self.SaveValues()
                if self.ElementType == "resource":
                    self.ParentWindow.RefreshView(variablepanel=False)
                self.RefreshValues()

        setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)

        def _MoveVariable(row, move):
            if self.Filter == "All":
                new_row = max(0, min(row + move, len(self.Values) - 1))
                if new_row != row:
                    self.Values.insert(new_row, self.Values.pop(row))
                    self.SaveValues()
                    self.RefreshValues()
                return new_row
            return row

        setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)

        def _GetRowEdit(row):
            row_edit = False
            if self:
                row_edit = self.Table.GetValueByName(row, "Edit")
                bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
                row_edit = row_edit or (bodytype in ["ST", "IL"])
            return row_edit

        def _RefreshButtons():
            if self:
                table_length = len(self.Table.data)
                row_edit = True
                row = 0
                if table_length > 0:
                    row = self.VariablesGrid.GetGridCursorRow()
                    row_edit = _GetRowEdit(row)
                self.AddButton.Enable(not self.Debug and self.showVarList)
                self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit) and self.showVarList)
                self.UpButton.Enable(
                    not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All") and self.showVarList)
                self.DownButton.Enable(
                    not self.Debug and (
                            table_length > 0 and row < table_length - 1 and self.Filter == "All") and self.showVarList)
                self.ClassFilter.Enable(self.showVarList)
                self.search_input.Enable(self.showVarList)

        setattr(self.VariablesGrid, "RefreshButtons", _RefreshButtons)

        panel_width = window.Parent.ScreenRect.Width - 35
        stretch_cols_width = panel_width
        stretch_cols_sum = 0
        if panel_width > self.PanelWidthMin:
            for col in range(len(self.ColSettings["fixed_size"])):
                if self.ColSettings["fixed_size"][col]:
                    stretch_cols_width -= self.ColSettings["size"][col]
                else:
                    stretch_cols_sum += self.ColSettings["size"][col]

        self.VariablesGrid.SetRowLabelSize(0)
        for col in range(self.Table.GetNumberCols()):
            attr = wx.grid.GridCellAttr()
            attr.SetAlignment(self.ColSettings["alignement"][col], wx.ALIGN_CENTRE)
            self.VariablesGrid.SetColAttr(col, attr)
            self.VariablesGrid.SetColMinimalWidth(col, self.ColSettings["size"][col])
            if (panel_width > self.PanelWidthMin) and not self.ColSettings["fixed_size"][col]:
                self.VariablesGrid.SetColSize(col, int(
                    (self.ColSettings["size"][col] / stretch_cols_sum) * stretch_cols_width))
            else:
                self.VariablesGrid.SetColSize(col, self.ColSettings["size"][col])

    def Save(self, refersh=True):
        if self.changed:
            self.SaveValues(refersh)
            self.changed = False

    def OnCommentButton(self, event):
        if hasattr(self.Parent, 'showComment'):
            if self.Parent.showComment:
                self.Parent.showComment = False
            else:
                self.Parent.showComment = True
            self.Parent.RefreshView()
        event.Skip()

    def OnImport(self, event):
        dialog = wx.FileDialog(self.ParentWindow,
                               _("Choose an csv file"),
                               '', "",
                               _("CSV files (*.csv)|*.csv"),
                               wx.FD_OPEN)
        if dialog.ShowModal() == wx.ID_OK:
            filepath = dialog.GetPath()
            with open(filepath, 'r', encoding='gbk') as f:
                lines = csv.DictReader(f)
                for line in lines:
                    if line['Name'] == '': continue
                    Name = line['Name']
                    Class = line['Class']
                    Type = line['Type']
                    Option = line['Option']
                    InitialValue = line['InitialValue']
                    Documentation = line['Documentation']
                    Location = line['Location']
                    if Class == 'Global' and not self.ElementType in ["config", "resource"]: continue
                    self.AddVariable(Name, Class, Type, Option, InitialValue, Documentation, Location)
                self.changed = True
                self.SaveValues()
                if self.ElementType == "resource":
                    self.ParentWindow.RefreshView(variablepanel=False)
                self.RefreshValues()
        event.Skip()

    def OnExport(self, event):
        dialog = wx.FileDialog(self, _("Choose a file"), '', "", _("CSV files (*.csv)|*.csv"),
                               wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
        if dialog.ShowModal() == wx.ID_OK:
            filepath = dialog.GetPath()
            with open(filepath, 'w', encoding='gbk') as f:
                myWriter = csv.writer(f)
                myWriter.writerow(['Name', 'Class', 'Type', 'Option', 'InitialValue', 'Documentation', 'Location'])
                for val in self.Values:
                    Name = val.Name
                    Class = val.Class
                    Type = val.Type
                    Option = val.Option
                    InitialValue = val.InitialValue
                    Documentation = val.Documentation
                    Location = val.Location
                    myWriter.writerow([Name, Class, Type, Option, InitialValue, Documentation, Location])
        event.Skip()

    def OnButtonShow(self, event):
        if self.VariablesGrid.IsEnabled():
            self.showVarList = False
            self.VariablesGrid.SetMaxSize(self.VariablesGrid.GetSize())
            self.VariablesGrid.Hide()
            self.VariablesGrid.Enable(False)
            self.Parent.SetSashInvisible(True)
        else:
            self.showVarList = True
            self.VariablesGrid.Show(True)
            self.VariablesGrid.Enable(True)
            self.Parent.SetSashInvisible(False)
        self.MainSizer.Layout()
        self.SetSizerAndFit(self.MainSizer, deleteOld=False)
        self.Parent.SetSashPosition(self.GetSize().height)

        ## @todo:恢复的变量窗口只能拉大不能减少
        self.Parent.SetSashGravity(0.2)
        self.Parent.SetMinimumPaneSize(20)

        wx.CallAfter(self.VariablesGrid.RefreshButtons)
        event.Skip()

    def OnGridDestroy(self, event):
        self.Save()
        # self.Table._updateColAttrs(self.VariablesGrid,destory=True)
        # for row in range(self.Table.GetNumberRows()):
        #     for col in range(self.Table.GetNumberCols()):
        #         currentEditor = self.VariablesGrid.GetCellEditor(row, col)
        #         # currentEditor.Reset()
        #         # if currentEditor.Control:
        #         #     currentEditor.Control=None
        #         # currentEditor.DecRef()
        #         self.VariablesGrid.SetCellRenderer(row, col, None)
        # self.VariablesGrid.CloseEditControl()
        # self.VariablesGrid.SetFocus()
        # self.VariablesGrid.Refresh()
        event.Skip()

    def mSearch(self, txt):
        t = txt.String
        self.Table.filter(t)
        self.VariablesGrid.Refresh()

    def __del__(self):
        self.RefreshHighlightsTimer.Stop()

    def SetTagName(self, tagname):
        self.TagName = tagname
        self.BodyType = self.Controler.GetEditedElementBodyType(self.TagName)

    def GetTagName(self):
        return self.TagName

    def IsFunctionBlockType(self, name):
        if isinstance(name, tuple) or \
                self.ElementType != "function" and self.BodyType in ["ST", "IL"]:
            return False
        else:
            return self.Controler.GetBlockType(name, debug=self.Debug) is not None

    def RefreshView(self, reload=False):
        self.PouNames = self.Controler.GetProjectPouNames(self.Debug)
        returnType = None
        description = None

        words = self.TagName.split("::")
        if self.ElementType == "config":
            self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug)
        elif self.ElementType == "resource":
            self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug)
        else:
            if self.ElementType == "function":
                self.ReturnType.Clear()
                for data_type in self.Controler.GetDataTypes(self.TagName, debug=self.Debug):
                    self.ReturnType.Append(data_type)
                returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, debug=self.Debug)
            description = self.Controler.GetPouDescription(words[1])
            self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, debug=self.Debug)

        if returnType is not None:
            self.ReturnType.SetStringSelection(returnType)
            self.ReturnType.Enable(not self.Debug)
            self.ReturnTypeLabel.Show()
            self.ReturnType.Show()
        else:
            self.ReturnType.Enable(False)
            self.ReturnTypeLabel.Hide()
            self.ReturnType.Hide()

        if description is not None:
            self.Description.SetValue(description)
            self.Description.Enable(not self.Debug)
            self.DescriptionLabel.Show()
            self.Description.Show()
        else:
            self.Description.Enable(False)
            self.DescriptionLabel.Hide()
            self.Description.Hide()

        self.RefreshValues()
        self.VariablesGrid.RefreshButtons()
        self.MainSizer.Layout()

    def OnReturnTypeChanged(self, event):
        words = self.TagName.split("::")
        self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
        self.Controler.BufferProject()
        self.ParentWindow.RefreshView(variablepanel=False)
        self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
        event.Skip()

    def OnDescriptionChanged(self, event):
        words = self.TagName.split("::")
        old_description = self.Controler.GetPouDescription(words[1])
        new_description = self.Description.GetValue()
        if new_description != old_description:
            self.Controler.SetPouDescription(words[1], new_description)
            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
        event.Skip()

    def OnClassFilter(self, event):
        self.Filter = self.FilterChoiceTransfer[self.VARIABLE_CHOICES_DICT[self.ClassFilter.GetStringSelection()]]
        self.RefreshTypeList()
        self.RefreshValues()
        self.VariablesGrid.RefreshButtons()
        event.Skip()

    def RefreshTypeList(self):
        if self.Filter == "All":
            self.ClassList = [self.FilterChoiceTransfer[choice] for choice in self.FilterChoices if
                              self.FilterChoiceTransfer[choice] not in ["All", "Interface", "Variables"]]
        elif self.Filter == "Interface":
            self.ClassList = ["Input", "Output", "InOut", "External"]
        elif self.Filter == "Variables":
            self.ClassList = ["Local", "Temp"]
        else:
            self.ClassList = [self.Filter]

    def ShowErrorMessage(self, message):
        dialog = wx.MessageDialog(self, message, _("Error"), wx.OK | wx.ICON_ERROR)
        dialog.ShowModal()
        dialog.Destroy()

    def OnVariableNameChange(self, old_name, new_name):
        """ propagate renaming of variable to the rest of the project """
        if old_name != "":
            self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_name, new_name)
        # self.SaveValues()
        self.Controler.BufferProject()
        wx.CallAfter(self.ParentWindow.RefreshView, False)
        self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)

    def CheckVariableName(self, value, row):
        if not TestIdentifier(value):
            message = _("\"%s\" is not a valid identifier!") % value
        elif value.upper() in IEC_KEYWORDS:
            message = _("\"%s\" is a keyword. It can't be used!") % value
        elif value.upper() in self.PouNames:
            message = _("A POU named \"%s\" already exists!") % value
        elif value.upper() in [var.Name.upper() for var in self.Values if var != self.Table.data[row]]:
            message = _("A variable with \"%s\" as name already exists in this pou!") % value
        else:
            return None
        return message

    def OnVariablesGridCellChange(self, event):
        row, col = event.GetRow(), event.GetCol()
        colname = self.Table.GetColLabelValue(col, False)
        value = self.Table.GetValue(row, col)
        self.Table.GetValue(row, 0)
        message = None

        if colname == "Name" and value != "":
            message = self.CheckVariableName(value, row)
            if message is None:
                self.SaveValues(False)
                old_value = self.Table.GetOldValue()
                self.OnVariableNameChange(old_value, value)
        else:
            self.SaveValues()
            if colname == "Class":
                self.ClearLocation(row, col, value)
                wx.CallAfter(self.ParentWindow.RefreshView)
            elif colname == "Location":
                wx.CallAfter(self.ParentWindow.RefreshView)

        if message is not None:
            wx.CallAfter(self.ShowErrorMessage, message)
            event.Veto()
        else:
            event.Skip()

    def ClearLocation(self, row, col, value):
        if self.Values[row].Location != '':
            if self.Table.GetColLabelValue(col, False) == 'Class' and value not in ["Local", "Global"] or \
                    self.Table.GetColLabelValue(col, False) == 'Type' and not self.Controler.IsLocatableType(value):
                self.Values[row].Location = ''
                self.RefreshValues()
                self.SaveValues()

    def BuildStdIECTypesMenu(self, type_menu):
        # build a submenu containing standard IEC types
        base_menu = wx.Menu(title='')
        for base_type in self.Controler.GetBaseTypes():
            new_id = wx.NewIdRef()
            base_menu.Append(id=new_id, helpString='', kind=wx.ITEM_NORMAL, item=base_type)
            self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)

        type_menu.Append(wx.NewIdRef(), _("Base Types"), base_menu)

    def BuildUserTypesMenu(self, type_menu):
        # build a submenu containing user-defined types
        datatype_menu = wx.Menu(title='')
        datatypes = self.Controler.GetDataTypes(basetypes=False, confnodetypes=False)
        for datatype in datatypes:
            new_id = wx.NewIdRef()
            datatype_menu.Append(id=new_id, helpString='', kind=wx.ITEM_NORMAL, item=datatype)
            self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)

        type_menu.Append(wx.NewIdRef(), _("User Data Types"), datatype_menu)

    def BuildLibsTypesMenu(self, type_menu):
        for category in self.Controler.GetConfNodeDataTypes():
            if len(category["list"]) > 0:
                # build a submenu containing confnode types
                confnode_datatype_menu = wx.Menu(title='')
                for datatype in category["list"]:
                    new_id = wx.NewIdRef()
                    confnode_datatype_menu.Append(id=new_id, helpString='', kind=wx.ITEM_NORMAL, item=datatype)
                    self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)

                type_menu.Append(wx.NewIdRef(), category["name"], confnode_datatype_menu)

    def BuildProjectTypesMenu(self, type_menu, classtype):
        # build a submenu containing function block types
        bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
        _pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
        if classtype in ["Input", "Output", "InOut", "External", "Global"] or \
                poutype != "function" and bodytype in ["ST", "IL"]:
            functionblock_menu = wx.Menu(title='')
            fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName)
            for functionblock_type in fbtypes:
                new_id = wx.NewIdRef()
                functionblock_menu.Append(id=new_id, helpString='', kind=wx.ITEM_NORMAL, item=functionblock_type)
                self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id)

            type_menu.Append(wx.NewIdRef(), _("Function Block Types"), functionblock_menu)

    def BuildArrayTypesMenu(self, type_menu):
        new_id = wx.NewIdRef()
        type_menu.Append(id=new_id, helpString='', kind=wx.ITEM_NORMAL, item=_("Array"))
        self.Bind(wx.EVT_MENU, self.VariableArrayTypeFunction, id=new_id)

    def OnVariablesGridEditorShown(self, event):
        row, col = event.GetRow(), event.GetCol()

        label_value = self.Table.GetColLabelValue(col, False)
        if label_value == "Type":
            old_value = self.Values[row].Type
            classtype = self.Table.GetValueByName(row, "Class")
            type_menu = wx.Menu(title='')  # the root menu

            self.BuildStdIECTypesMenu(type_menu)

            self.BuildUserTypesMenu(type_menu)

            self.BuildLibsTypesMenu(type_menu)

            self.BuildProjectTypesMenu(type_menu, classtype)

            self.BuildArrayTypesMenu(type_menu)

            rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
            corner_x = rect.x + rect.width
            corner_y = rect.y + self.VariablesGrid.GetColLabelSize()

            # pop up this new menu
            self.VariablesGrid.PopupMenu(type_menu, corner_x, corner_y)
            type_menu.Destroy()
            event.Veto()
            value = self.Values[row].Type
            if old_value != value:
                self.ClearLocation(row, col, value)
        else:
            event.Skip()

    def GetVariableTypeFunction(self, base_type):
        def VariableTypeFunction(event):
            row = self.VariablesGrid.GetGridCursorRow()
            self.Table.SetValueByName(row, "Type", base_type)
            self.Table.ResetView(self.VariablesGrid)
            self.SaveValues(False)
            self.ParentWindow.RefreshView(variablepanel=False)
            self.Controler.BufferProject()
            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)

        return VariableTypeFunction

    def VariableArrayTypeFunction(self, event):
        row = self.VariablesGrid.GetGridCursorRow()
        dialog = ArrayTypeDialog(self,
                                 self.Controler.GetDataTypes(self.TagName),
                                 self.Table.GetValueByName(row, "Type"))
        if dialog.ShowModal() == wx.ID_OK:
            self.Table.SetValueByName(row, "Type", dialog.GetValue())
            self.Table.ResetView(self.VariablesGrid)
            self.SaveValues(False)
            self.ParentWindow.RefreshView(variablepanel=False)
            self.Controler.BufferProject()
            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
        dialog.Destroy()

    def OnVariablesGridCellLeftClick(self, event):
        row = event.GetRow()
        if not self.Debug and (event.GetCol() == 0 and self.Table.GetValueByName(row, "Edit")):
            var_name = self.Table.GetValueByName(row, "Name")
            var_class = self.Table.GetValueByName(row, "Class")
            var_type = self.Table.GetValueByName(row, "Type")
            var_doc = self.Table.GetValueByName(row, "Documentation")
            data = wx.TextDataObject(str((var_name, var_class, var_type, self.TagName, var_doc)))
            dragSource = wx.DropSource(self.VariablesGrid)
            dragSource.SetData(data)
            dragSource.DoDragDrop()
        event.Skip()

    def RefreshValues(self):
        data = []
        for num, variable in enumerate(self.Values):
            if variable.Class in self.ClassList:
                variable.Number = num + 1
                data.append(variable)
        self.Table.SetData(data)
        self.Table.ResetView(self.VariablesGrid)

    def SaveValues(self, buffer=True):
        words = self.TagName.split("::")
        if self.ElementType == "config":
            self.Controler.SetConfigurationGlobalVars(words[1], self.Values)
        elif self.ElementType == "resource":
            self.Controler.SetConfigurationResourceGlobalVars(words[1], words[2], self.Values)
        else:
            if self.ReturnType.IsEnabled():
                self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
            self.Controler.SetPouInterfaceVars(words[1], self.Values)
        if buffer:
            self.Controler.BufferProject()

    def SaveValue(self, row, buffer=True):
        value = self.Values[row - 1]
        words = self.TagName.split("::")
        if self.ElementType == "config":
            self.Controler.SetConfigurationGlobalVar(words[1], value)
        elif self.ElementType == "resource":
            self.Controler.SetConfigurationResourceGlobalVar(words[1], words[2], value)
        else:
            if self.ReturnType.IsEnabled():
                self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
            self.Controler.SetPouInterfaceVar(words[1], row, value)
        if buffer:
            self.Controler.BufferProject()
            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)

    # -------------------------------------------------------------------------------
    #                        Highlights showing functions
    # -------------------------------------------------------------------------------

    def OnRefreshHighlightsTimer(self, event):
        self.Table.ResetView(self.VariablesGrid)
        event.Skip()

    def AddVariable(self, Name, Class, Type, Option="", InitialValue="", Documentation="", Location=""):
        row_content = self.DefaultValue.copy()
        row_content.Name = Name
        row_content.Class = Class
        row_content.Type = Type
        row_content.Option = Option
        row_content.InitialValue = InitialValue
        row_content.Documentation = Documentation
        row_content.Location = Location
        self.Values.append(row_content)
        new_row = self.Table.GetNumberRows()

        return new_row

    def AddVariableHighlight(self, infos, highlight_type):
        if isinstance(infos[0], tuple):
            for i in range(*infos[0]):
                self.Table.AddHighlight(self.VariablesGrid, (i,) + infos[1:], highlight_type=highlight_type)
            cell_visible = infos[0][0]
        else:
            self.Table.AddHighlight(self.VariablesGrid, infos, highlight_type=highlight_type)
            cell_visible = infos[0]
        colnames = [colname.lower() for colname in self.Table.colnames]
        self.VariablesGrid.MakeCellVisible(cell_visible, colnames.index(infos[1]))
        self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)

    def RemoveVariableHighlight(self, infos, highlight_type):
        if isinstance(infos[0], tuple):
            for i in range(*infos[0]):
                self.Table.RemoveHighlight(self.VariablesGrid, (i,) + infos[1:], highlight_type=highlight_type)
        else:
            self.Table.RemoveHighlight(self.VariablesGrid, infos, highlight_type)
        self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)

    def ClearHighlights(self, highlight_type=None):
        self.Table.ClearHighlights(highlight_type)
        self.Table.ResetView(self.VariablesGrid)

    def GenerateNewName(self, tagname, name: str, forma, start_idx=0, exclude=None, debug=False):
        if name is not None:
            result = re.search(VARIABLE_NAME_SUFFIX_MODEL, name)
            if result is not None:
                forma = name[:result.start(1)] + '%d'
                start_idx = int(result.group(1))
            else:
                forma = name + '%d'
        names = list(map(lambda x: x.Name.upper(), self.Values))
        i = start_idx
        while name is None or name.upper() in names:
            name = (forma % i)
            i += 1
        return name
